package com.wl.spring.web;

import com.wl.spring.AutowireAnnoConfig;
import com.wl.spring.biz.UserBiz;
import lombok.Data;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.stream.Stream;

public class MyAnnotationConfigApplicationContest {
    Class configClass;
    Object configObject;

    Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    protected Map<String,Object> singtonObjects = new HashMap<>();

    public MyAnnotationConfigApplicationContest(Class configClass) throws Exception {
        this.configClass = configClass;
        configObject = configClass.newInstance();

        //读取配置（基于注解） 创建 BeaDefinition 对象，存入BeanDefinitionMap 容器
        addBeanDefinitionFormMethod();
        addBeanDefinitionFromPackage();
        beanDefinitionMap.forEach((id,bean)->{
            System.out.printf("%s : %s \n", id, bean);
        });

        //根据 BeaDefinition 创建 Bean ，存入 IOC singtonObjects
        createBean();

        //后续实现 getbean

        //实现自动装载 autowired
    }

    public Object getBean(String id){
        Object bean = singtonObjects.get(id);
        if(bean == null){
            throw new RuntimeException("没有该bean:" + id);
        }
        return bean;
    }

    //泛型 无法通过泛型来找
    public <T> T getBean(Class<T> tClass){
        //stream 编程
        //值集合
        List<Map.Entry> list = singtonObjects.entrySet()
                .stream()  //转成stream
                .filter(entry->entry.getValue().getClass().isAssignableFrom(tClass))  //过滤
                .collect(Collections.list());
        if(list.isEmpty()){
            throw new RuntimeException("没有该bean:" + tClass);
        } else if (list.size() == 1) { // 刚好找到唯一的bean
            return (T) list.get(0).getValue();
        }else{ //如果通过类型找到多个bean 找朱bean
            List<Map.Entry> list1 = list.stream()
                    .filter(entry -> {
                        BeanDefinition bd = beanDefinitionMap.get(entry.getKey());
                        return bd.isPrimary();
                    })
                    .collect(Collections.toList);
            if(list1.isEmpty()){ //如果没有定义到主bean
                throw new RuntimeException("该bean没有定义primary bean:" + tClass);
            }else if (list1.size() == 1){
                return (T) list1.get(0).getValue();
            }else{  //否则定义了多个主bean
                throw new RuntimeException("该bean定义了多个primary bean:" + tClass);
            }
        }
    }

    private void createBean() throws Exception, IllegalAccessException {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String id = entry.getKey();
            BeanDefinition bd = entry.getValue();
            if(bd.beanMethod!=null){
                //仅执行无参数的bean方法
                if(bd.beanMethod.getParameterCount()==0){
                    bd.beanMethod.setAccessible(true);
                    Object bean = bd.beanMethod.invoke(configObject);
                    singtonObjects.put(id,bean);
                }else{
                    //通过反射
                    Object bean = bd.beanClass.newInstance();
                    singtonObjects.put(id,bean);
                }
                bd.beanMethod.invoke(configObject);
                
            }
        }
    }

    private void addBeanDefinitionFromPackage() throws Exception {
        ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if (componentScan != null) {
            String[] packages = componentScan.value();
            URL url = configClass.getClassLoader().getResource("");
            //编译目录路径
            String classPath = URLDecoder.decode(url.getPath().substring(1));
            //编译之后获取扫描的根路径
            String scanRootPath;
            List<File> classFileList = new ArrayList<>();
            File classesFile = new File(classPath);
            for (String aPackage : packages) {
                //在这输出包路径
                System.out.println("aPackage = " + aPackage);
                //在这输出类路径
                scanRootPath = classPath + aPackage.replaceAll("\\.","/");
                scanClassFile(new File(scanRootPath),classFileList);
            }
            for (File classFile : classFileList) {
                classPath = classesFile.getAbsolutePath()
                        .replace(classesFile.getAbsolutePath(), "");
                classPath = classPath.replace(".class","");
                classPath = classPath.substring(1);
                classPath = classPath.replaceAll("\\\\",".");
                System.out.println("classPath = " + classPath);
                Class beanClass = Class.forName(classPath);

                //判断是不是主键  四个主键注解
                Service service = (Service) beanClass.getAnnotation(Service.class);
                Repository repository = (Repository) beanClass.getAnnotation(Repository.class);
                Controller controller = (Controller) beanClass.getAnnotation(Controller.class);
                Component component = (Component) beanClass.getAnnotation(Component.class);

                //如果四个主键注解都为空，那么就不是主键。
                if(service == null &&
                        repository == null &&
                        controller == null &&
                        component == null) {
                    continue;
                }

                //经过上面的判断出是主键，那么就到这一步来 构建beanDefinition
                BeanDefinition bd = new BeanDefinition();
                bd.beanClass = beanClass;
                bd.lazy = beanClass.getAnnotation(Lazy.class) != null;
                bd.primary = beanClass.getAnnotation(Lazy.class)!=null;
                if(beanClass.getAnnotation(Scope.class)!= null){
                    bd.scope = ((Scope)beanClass.getAnnotation(Scope.class)).value();
                }
                //判断是那个主键不为空
                if(service!=null && service.value().isBlank() ==false){
                    service.id = service.value();
                } else if (repository!=null && repository.value().isBlank() ==false) {
                    repository.id = repository.value();
                } else if (controller!=null && controller.value().isBlank() ==false) {
                    controller.id = controller.value();                    
                } else if (component!=null && component.value().isBlank() ==false) {
                    component.id = component.value();
                }else{
                    String id = beanClass.getSimpleName();
                    //             取第一个字符把id首字母小写    取首字母后面一个字符，保留原样
                    id = id.substring(0, 1).toLowerCase() + id.substring(1);
                    bd.id = id;
                }
                beanDefinitionMap.put(bd.id,bd);
            }
        }
    }

    private  void scanClassFile(File dir, List<File> classFileList){
        File[] classFiles = dir.listFiles(file -> file.getName().endsWith(".class"));
        for (File classFile : classFiles) {
            classFileList.add(classFile);
        }
        File[] subDirs = dir.listFiles(file ->file.isDirectory());
        for (File subDir : subDirs) {
            scanClassFile(subDir, classFileList);
        }
    }

    private void addBeanDefinitionFormMethod() {
        Method[] declaredMethods = configClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //判断是否是bean方法
            if (method.getAnnotation(Bean.class)!=null) {
                //Bean方法
                BeanDefinition  bd = new BeanDefinition();
                bd.beanMethod = method;
                bd.beanClass = method.getReturnType();
                bd.lazy = method.getAnnotation(Lazy.class)!=null;
                bd.primary = method.getAnnotation(Lazy.class)!=null;
                Bean beanAnno = method.getAnnotation(Bean.class);
                if(beanAnno.value().length>0){
                    bd.id = beanAnno.value()[0];
                }else{
                    bd.id = method.getName();
                }
                if(method.getAnnotation(Scope.class)!= null){
                    bd.scope = method.getAnnotation(Scope.class).value();
                }
                beanDefinitionMap.put(bd.id,bd);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        MyAnnotationConfigApplicationContest cxt;
        cxt = new MyAnnotationConfigApplicationContest(AutowireAnnoConfig.class);

        System.out.println("--------------------");
        Object userMapper = cxt.getBean("userMapper");
        //打印
        System.out.println("userMapper = " + userMapper);

        System.out.println("----------------------");
        UserBiz userBiz = cxt.getBean(UserBiz.class);
        System.out.println("userBiz = " + userBiz);
    }
}

@Data
class BeanDefinition{
    String id;
    Class beanClass;
    Method beanMethod;
    boolean lazy;
    String scope;
    boolean primary;
    //TODO 扩展其他属性
}
